package atguigu.com.login.service.impl;


import atguigu.com.login.VO.LoginForm;
import atguigu.com.login.VO.MetaVo;
import atguigu.com.login.VO.RouterVo;
import atguigu.com.login.exception.Guliexception;
import atguigu.com.login.mapper.SysMenuMapper;
import atguigu.com.login.mapper.SysUserMapper;
import atguigu.com.login.pojo.SysMenu;
import atguigu.com.login.pojo.SysUser;
import atguigu.com.login.service.SysUserService;
import atguigu.com.login.util.JwtInfo;
import atguigu.com.login.util.JwtUtils;
import atguigu.com.login.util.MD5;
import atguigu.com.login.util.MenuHelper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import net.sf.jsqlparser.expression.DateTimeLiteralExpression;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static atguigu.com.login.util.JwtUtils.APP_SECRET;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author zhuzhu
 * @since 2024-01-14
 */
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private final ObjectMapper objectMapper = new ObjectMapper();
    @Autowired
    private SysMenuMapper sysMenuMapper;

    @Override
    public String login(LoginForm loginForm) {
        String username = loginForm.getUsername();//对应前三条字节码
        String password = loginForm.getPassword();//对应前六条
        QueryWrapper<SysUser> wrapper = new QueryWrapper<>();//对应前10条
        wrapper.eq("username", username);
        SysUser sysUser = baseMapper.selectOne(wrapper);//对应前22条
        if (sysUser == null) {
            throw new Guliexception(20001, "用户名不存在!");
        }
        if (!sysUser.getPassword().equals(MD5.encrypt(password))){
            throw new Guliexception(20001, "用户名或密码错误!");
        }
        if (sysUser.getStatus().intValue() == 0) {
            throw new Guliexception(20001, "抱歉!当前用户已停用!");
        }
        //走到这步就证明没问题，往前端写token。不过这这里,只是为了生产一个token字符串而已.
        String token = getJwtToken(sysUser);//前57条
        try {
            stringRedisTemplate.boundValueOps(token).set(objectMapper.writeValueAsString(sysUser));
            stringRedisTemplate.boundValueOps(token).expire(30, TimeUnit.MINUTES);
        } catch (Exception e) {
            e.printStackTrace();
            throw new Guliexception(20001, "用户信息写入redis失败!");
        }
        return token;
    }

    @Override
    public SysUser getSysUserInfoByToken(String token) {
        String sysUserString = stringRedisTemplate.boundValueOps(token).get();
        if (StringUtils.isEmpty(sysUserString)) {
            throw new Guliexception(20001, "token已过期请重新登录!");
        }
        SysUser sysUser = null;
        try {
            sysUser = objectMapper.readValue(sysUserString, SysUser.class);
        } catch (Exception e) {
            throw new Guliexception(20001, "字符串转换成SysUser失败!");
        }
        return sysUser;
    }

    @Override
    public List<RouterVo> findMenusByUserId(Long userId) {
        List<SysMenu> menus = null;
        if (userId.intValue() == 1) {
            //proving this user is admin
            QueryWrapper<SysMenu> wrapper = new QueryWrapper<>();
            wrapper.eq("status", 1).orderByAsc("sort_value");
            menus = sysMenuMapper.selectList(wrapper);
        } else {
            menus = sysMenuMapper.getMenusByUserId(userId);
        }
        List<SysMenu> rootNodes = MenuHelper.selectRootNodes(menus);
        List<SysMenu> nodesTree = new ArrayList<>();//这是个根节点的集合，如果它有孩子，就会给它封装进去。
        for (SysMenu rootNode : rootNodes) {
            MenuHelper.injectChildren(rootNode, menus);
            nodesTree.add(rootNode);
        }
        //下一步就需要你把树形结构的节点变成前端需要的router
        List<RouterVo> routerVos = buildRouter(nodesTree);
        return routerVos;
    }

    @Override
    public List<String> findButtonByUserId(Long userId) {
        List<SysMenu> menus = null;
        if (userId.intValue() == 1) {
            //proving this user is admin
            QueryWrapper<SysMenu> wrapper = new QueryWrapper<>();
            wrapper.eq("status", 1).orderByAsc("sort_value");
            menus = sysMenuMapper.selectList(wrapper);
        } else {
            menus = sysMenuMapper.getMenusByUserId(userId);
        }
        List<String> buttons = menus.stream()
                .filter(item -> item.getType().intValue() == 2)
                .map(item -> item.getPerms()).collect(Collectors.toList());
        return buttons;
    }

    private List<RouterVo> buildRouter(List<SysMenu> nodesTree) {
        List<RouterVo> routerVos = new ArrayList<>();
        for (SysMenu item : nodesTree) {
            RouterVo routerVo = new RouterVo();
            routerVo.setPath(makePath(item));
            routerVo.setComponent(item.getComponent());
            routerVo.setHidden(false);
            routerVo.setAlwaysShow(true);
            routerVo.setMeta(new MetaVo(item.getName(), item.getIcon()));
            if (item.getChildren().size() > 0) {
                checkingTypeOneForCreatingSameLevelRouterOfHidden(item,routerVos);
                List<RouterVo> routerForChildren = buildRouter(item.getChildren());
                routerVo.setChildren(routerForChildren);
            }
            routerVos.add(routerVo);//把路由加到盘子中
        }
        //这里循环已经结束
        return routerVos;
    }

    /**
     *
     * @param item
     * @param routerVos
     */
    private void checkingTypeOneForCreatingSameLevelRouterOfHidden(SysMenu item,List<RouterVo> routerVos){
        if (item.getType().intValue() == 1) {
            //走这里证明它是一个菜单，0是目录
            List<SysMenu> itemChildren = item.getChildren();//拿到了按钮
            List<SysMenu> buttonOfHavingComponent = itemChildren.stream().filter(itemChild -> !StringUtils
                            .isEmpty(itemChild.getComponent()))
                    .collect(Collectors.toList());//有component的按钮
            creatingTheSameLevelRouterOfHiddenByCheckingChildren(routerVos,buttonOfHavingComponent);
        }//这个部分仅限type为1的菜单会执行
    }
    private void creatingTheSameLevelRouterOfHiddenByCheckingChildren(List<RouterVo> routerVos,
                                                                 List<SysMenu> buttonOfHavingComponent){
        if (buttonOfHavingComponent != null && buttonOfHavingComponent.size() > 0) {
            for (SysMenu button : buttonOfHavingComponent) {
                RouterVo routerForHidden = new RouterVo();
                routerForHidden.setPath(makePath(button));
                routerForHidden.setComponent(button.getComponent());
                routerForHidden.setHidden(true);
                routerForHidden.setAlwaysShow(false);
                routerForHidden.setMeta(new MetaVo(button.getName(), button.getIcon()));
                routerVos.add(routerForHidden);
            }
        }
    }

    private String makePath(SysMenu sysMenu) {
        if (sysMenu.getParentId().intValue() == 0) {
            return "/" + sysMenu.getPath();
        } else {
            return sysMenu.getPath();
        }
    }

    public static String getJwtToken(SysUser sysUser) {

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("guli-user")//主题
                .setIssuedAt(new Date())//颁发时间
                .claim("id", sysUser.getId())//用户id
                .claim("name", sysUser.getName())//用户昵称
                .claim("avatar", sysUser.getHeadUrl())//用户头像
                .signWith(SignatureAlgorithm.HS256, getKeyInstance())
                .compact();

        return JwtToken;
    }

    private static Key getKeyInstance() {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        byte[] bytes = DatatypeConverter.parseBase64Binary(APP_SECRET);
        return new SecretKeySpec(bytes, signatureAlgorithm.getJcaName());
    }
}
